"""
PatternHandlers are a way of expressing an Expect block in Python, using a class.

Copyright (c) 2009 Christopher Hesse
http://code.google.com/p/python-expect/
"""

import threading
from .exceptions import Timeout, ChannelClosed

pattern_counter_lock = threading.RLock()
pattern_counter = 0

def pattern(pattern):
    """
    Pattern decorator, use this to create pattern handling methods on a
    PatternHandler.
    """
    def decorator(f):
        global pattern_counter
        with pattern_counter_lock:
            f.numbered_pattern = (pattern_counter, pattern)
            pattern_counter += 1
        return f
    return decorator


class Continue(object):
    """
    Continue running the pattern handler.
    """
    pass


class PatternHandler(object):
    """
    This class enables the user to create a pattern handling class that can
    be used with the expect() function or the .expect() method of an 
    Expectable object.
    
    Here's how to use it:

        class QuestionHandler(PatternHandler):
            @pattern("y/n:")
            def yes_no_prompt(self, result, channel):
                channel.send("y")
                return Continue

            @pattern(">")
            def shell_prompt(self, result, channel):
                print "we're done here"
                return "success"
            
        out = expect(channel, QuestionHandler(), timeout)
            
    This is pretty much equivalent to the following:
        
        import time
        start_time = time.time()
        out = None
        while True:
            elapsed = time.time() - start_time
            remaining = timeout - elapsed
            result = expect(channel, [
                ("yes_no_prompt", "y/n:"),
                ("shell_prompt", ">"),
            ], remaining)
            
            if result == "yes_no_prompt":
                channel.send("y")
            elif result == "shell_prompt":
                print "we're done here"
                out = "success"
                break
    
    Except, perhaps, a bit easier to read.  In effect, it is similar
    to Tcl's expect {} block.
    
    Each pattern handling method takes 3 parameters, the self object,
    the result of the match, and the channel that matched the pattern
    provided in the @pattern decorator.
    
    This decorator works with anything that you can use in a normal
    expect command, like a regular expression object.

        class QuestionHandler(PatternHandler):
            @pattern(re.compile("[Pp]assword:"))
            def password(self, result, channel):
                print "password"

            @pattern(TextPattern("* * *"))
            def heading(self, result, channel):
                print "heading"
    
    Within each pattern handling method, you can return Continue to
    keep the handler going. Any other return value (including none at all)
    causes the handler to stop and expect returns that value.
    
    There are two special methods, 'timeout' and 'channel_closed' that handle
    those two cases.
    
    The 'timeout' method is called when either no patterns are matched for
    the specified timeout, or all patterns that do match return Continue.
    
    The 'channel_closed' method is called when z channel closes while it is
    being expected on.
    
    The only other feature of note is that you can call .reset_timer([seconds])
    if you want to set a new timer on the next expect operation or reset the
    existing one.  You should return Continue if you do this, otherwise it won't
    do much good.
    """
    
    def __init__(self):
        self._patterns = self._get_patterns()
        
    def _get_patterns(self):
        """
        Look at all methods of this class instance and find all that have
        a numbered_patterns attribute, these are methods meant to handle
        pattern matches, indicated as such with the @pattern decorator.
        """
        numbered_patterns = []
        for attribute_name in dir(self):
            if not attribute_name.startswith('_'):
                method = getattr(self, attribute_name)
                numbered_pattern = getattr(method, 'numbered_pattern', None)
                if numbered_pattern is not None:
                    numbered_patterns.append(numbered_pattern + (method,))
        numbered_patterns.sort()
        return [(pattern, method) \
                    for (n, pattern, method) in numbered_patterns]
                    
    def reset_timer(self, new_timeout=None):
        """
        Reset the timeout timer to its original value or the specified
        new_timeout.
        """
        if new_timeout is None:
            self.new_timeout = self.timeout
        else:
            self.new_timeout = new_timeout
    
    def timeout(self, remaining_channels):
        """
        Handle the case when either no patterns are matched for the specified
        timeout, or all patterns that do match return Continue.
        """
        message = "Timeout while waiting on {0} channels"
        message = message.format(len(remaining_channels))
        raise Timeout(message, remaining_channels)
        
    def channel_closed(self, channel):
        """
        Handle the case when an expectable's channel closes while it is being
        expected upon.
        """
        raise ChannelClosed(channel)